/*
 *  Bootstrap Duallistbox - v3.0.5
 *  A responsive dual listbox widget optimized for Twitter Bootstrap. It works on all modern browsers and on touch devices.
 *  http://www.virtuosoft.eu/code/bootstrap-duallistbox/
 *
 *  Made by István Ujj-Mészáros
 *  Under Apache License v2.0 License
 */
;(function ($, window, document, undefined) {
    // Create the defaults once
    var pluginName = 'bootstrapDualListbox',
        defaults = {
            bootstrap2Compatible: false,
            filterTextClear: '显示全部',
            filterPlaceHolder: '键入关键词筛选',
            moveSelectedLabel: '移动选中项',
            moveAllLabel: '移动全部',
            removeSelectedLabel: '删除选中项',
            removeAllLabel: '删除全部',
            moveOnSelect: true,                                                                 // true/false (forced true on androids, see the comment later)
            preserveSelectionOnMove: false,                                                     // 'all' / 'moved' / false
            selectedListLabel: false,                                                           // 'string', false
            nonSelectedListLabel: false,                                                        // 'string', false
            helperSelectNamePostfix: '_helper',                                                 // 'string_of_postfix' / false
            selectorMinimalHeight: 100,
            showFilterInputs: true,                                                             // whether to show filter inputs
            nonSelectedFilter: '',                                                              // string, filter the non selected options
            selectedFilter: '',                                                                 // string, filter the selected options
            infoText: '数据总数{0}',                                                        // text when all options are visible / false for no info text
            infoTextFiltered: '<span class="label label-warning">筛选</span> {0} 从 {1}', // when not all of the options are visible due to the filter
            infoTextEmpty: '空列表',                                                        // when there are no options present in the list
            filterOnValues: false                                                               // filter by selector's values, boolean
            , buttonClass: 'btn-white btn-bold'//ACE
        },
    // Selections are invisible on android if the containing select is styled with CSS
    // http://code.google.com/p/android/issues/detail?id=16922
        isBuggyAndroid = /android/i.test(navigator.userAgent.toLowerCase());

    // The actual plugin constructor
    function BootstrapDualListbox(element, options) {
        this.element = $(element);
        // jQuery has an extend method which merges the contents of two or
        // more objects, storing the result in the first object. The first object
        // is generally empty as we don't want to alter the default options for
        // future instances of the plugin
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;
        this.init();
    }

    function triggerChangeEvent(dualListbox) {
        dualListbox.element.trigger('change');
    }

    function updateSelectionStates(dualListbox) {
        dualListbox.element.find('option').each(function (index, item) {
            var $item = $(item);
            if (typeof($item.data('original-index')) === 'undefined') {
                $item.data('original-index', dualListbox.elementCount++);
            }
            if (typeof($item.data('_selected')) === 'undefined') {
                $item.data('_selected', false);
            }
        });
    }

    function changeSelectionState(dualListbox, original_index, selected) {
        dualListbox.element.find('option').each(function (index, item) {
            var $item = $(item);
            if ($item.data('original-index') === original_index) {
                $item.prop('selected', selected);
            }
        });
    }

    function formatString(s, args) {
        return s.replace(/\{(\d+)\}/g, function (match, number) {
            return typeof args[number] !== 'undefined' ? args[number] : match;
        });
    }

    function refreshInfo(dualListbox) {
        if (!dualListbox.settings.infoText) {
            return;
        }

        var visible1 = dualListbox.elements.select1.find('option').length,
            visible2 = dualListbox.elements.select2.find('option').length,
            all1 = dualListbox.element.find('option').length - dualListbox.selectedElements,
            all2 = dualListbox.selectedElements,
            content = '';

        if (all1 === 0) {
            content = dualListbox.settings.infoTextEmpty;
        } else if (visible1 === all1) {
            content = formatString(dualListbox.settings.infoText, [visible1, all1]);
        } else {
            content = formatString(dualListbox.settings.infoTextFiltered, [visible1, all1]);
        }

        dualListbox.elements.info1.html(content);
        dualListbox.elements.box1.toggleClass('filtered', !(visible1 === all1 || all1 === 0));

        if (all2 === 0) {
            content = dualListbox.settings.infoTextEmpty;
        } else if (visible2 === all2) {
            content = formatString(dualListbox.settings.infoText, [visible2, all2]);
        } else {
            content = formatString(dualListbox.settings.infoTextFiltered, [visible2, all2]);
        }

        dualListbox.elements.info2.html(content);
        dualListbox.elements.box2.toggleClass('filtered', !(visible2 === all2 || all2 === 0));
    }

    function refreshSelects(dualListbox) {
        dualListbox.selectedElements = 0;

        dualListbox.elements.select1.empty();
        dualListbox.elements.select2.empty();

        dualListbox.element.find('option').each(function (index, item) {
            var $item = $(item);
            if ($item.prop('selected')) {
                dualListbox.selectedElements++;
                dualListbox.elements.select2.append($item.clone(true).prop('selected', $item.data('_selected')));
            } else {
                dualListbox.elements.select1.append($item.clone(true).prop('selected', $item.data('_selected')));
            }
        });

        if (dualListbox.settings.showFilterInputs) {
            filter(dualListbox, 1);
            filter(dualListbox, 2);
        }
        refreshInfo(dualListbox);
    }

    function filter(dualListbox, selectIndex) {
        if (!dualListbox.settings.showFilterInputs) {
            return;
        }

        saveSelections(dualListbox, selectIndex);

        dualListbox.elements['select' + selectIndex].empty().scrollTop(0);
        var regex = new RegExp($.trim(dualListbox.elements['filterInput' + selectIndex].val()), 'gi'),
            allOptions = dualListbox.element.find('option'),
            options = dualListbox.element;

        if (selectIndex === 1) {
            options = allOptions.not(':selected');
        } else {
            options = options.find('option:selected');
        }

        options.each(function (index, item) {
            var $item = $(item),
                isFiltered = true;
            if (item.text.match(regex) || (dualListbox.settings.filterOnValues && $item.attr('value').match(regex) )) {
                isFiltered = false;
                dualListbox.elements['select' + selectIndex].append($item.clone(true).prop('selected', $item.data('_selected')));
            }
            allOptions.eq($item.data('original-index')).data('filtered' + selectIndex, isFiltered);
        });

        refreshInfo(dualListbox);
    }

    function saveSelections(dualListbox, selectIndex) {
        var options = dualListbox.element.find('option');
        dualListbox.elements['select' + selectIndex].find('option').each(function (index, item) {
            var $item = $(item);
            options.eq($item.data('original-index')).data('_selected', $item.prop('selected'));
        });
    }

    function sortOptions(select) {
        select.find('option').sort(function (a, b) {
            return ($(a).data('original-index') > $(b).data('original-index')) ? 1 : -1;
        }).appendTo(select);
    }

    function clearSelections(dualListbox) {
        dualListbox.elements.select1.find('option').each(function () {
            dualListbox.element.find('option').data('_selected', false);
        });
    }

    function move(dualListbox) {
        if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) {
            saveSelections(dualListbox, 1);
            saveSelections(dualListbox, 2);
        } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) {
            saveSelections(dualListbox, 1);
        }

        dualListbox.elements.select1.find('option:selected').each(function (index, item) {
            var $item = $(item);
            if (!$item.data('filtered1')) {
                changeSelectionState(dualListbox, $item.data('original-index'), true);
            }
        });

        refreshSelects(dualListbox);
        triggerChangeEvent(dualListbox);
        sortOptions(dualListbox.elements.select2);
    }

    function remove(dualListbox) {
        if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) {
            saveSelections(dualListbox, 1);
            saveSelections(dualListbox, 2);
        } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) {
            saveSelections(dualListbox, 2);
        }

        dualListbox.elements.select2.find('option:selected').each(function (index, item) {
            var $item = $(item);
            if (!$item.data('filtered2')) {
                changeSelectionState(dualListbox, $item.data('original-index'), false);
            }
        });

        refreshSelects(dualListbox);
        triggerChangeEvent(dualListbox);
        sortOptions(dualListbox.elements.select1);
    }

    function moveAll(dualListbox) {
        if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) {
            saveSelections(dualListbox, 1);
            saveSelections(dualListbox, 2);
        } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) {
            saveSelections(dualListbox, 1);
        }

        dualListbox.element.find('option').each(function (index, item) {
            var $item = $(item);
            if (!$item.data('filtered1')) {
                $item.prop('selected', true);
            }
        });

        refreshSelects(dualListbox);
        triggerChangeEvent(dualListbox);
    }

    function removeAll(dualListbox) {
        if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) {
            saveSelections(dualListbox, 1);
            saveSelections(dualListbox, 2);
        } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) {
            saveSelections(dualListbox, 2);
        }

        dualListbox.element.find('option').each(function (index, item) {
            var $item = $(item);
            if (!$item.data('filtered2')) {
                $item.prop('selected', false);
            }
        });

        refreshSelects(dualListbox);
        triggerChangeEvent(dualListbox);
    }

    function bindEvents(dualListbox) {
        dualListbox.elements.form.submit(function (e) {
            if (dualListbox.elements.filterInput1.is(':focus')) {
                e.preventDefault();
                dualListbox.elements.filterInput1.focusout();
            } else if (dualListbox.elements.filterInput2.is(':focus')) {
                e.preventDefault();
                dualListbox.elements.filterInput2.focusout();
            }
        });

        dualListbox.element.on('bootstrapDualListbox.refresh', function (e, mustClearSelections) {
            dualListbox.refresh(mustClearSelections);
        });

        dualListbox.elements.filterClear1.on('click', function () {
            dualListbox.setNonSelectedFilter('', true);
        });

        dualListbox.elements.filterClear2.on('click', function () {
            dualListbox.setSelectedFilter('', true);
        });

        dualListbox.elements.moveButton.on('click', function () {
            move(dualListbox);
        });

        dualListbox.elements.moveAllButton.on('click', function () {
            moveAll(dualListbox);
        });

        dualListbox.elements.removeButton.on('click', function () {
            remove(dualListbox);
        });

        dualListbox.elements.removeAllButton.on('click', function () {
            removeAll(dualListbox);
        });

        dualListbox.elements.filterInput1.on('change keyup', function () {
            filter(dualListbox, 1);
        });

        dualListbox.elements.filterInput2.on('change keyup', function () {
            filter(dualListbox, 2);
        });
    }

    BootstrapDualListbox.prototype = {
        init: function () {
            // Add the custom HTML template
            this.container = $('' +
                '<div class="bootstrap-duallistbox-container">' +
                ' <div class="box1">' +
                '   <label></label>' +
                '   <span class="info-container">' +
                '     <span class="info"></span>' +
                '     <button type="button" class="btn clear1 pull-right"></button>' +
                '   </span>' +
                '   <input class="filter" type="text">' +
                '   <div class="btn-group buttons">' +
                '     <button type="button" class="btn moveall">' +
                '       <i></i>' +
                '       <i></i>' +
                '     </button>' +
                '     <button type="button" class="btn move">' +
                '       <i></i>' +
                '     </button>' +
                '   </div>' +
                '   <select multiple="multiple"></select>' +
                ' </div>' +
                ' <div class="box2">' +
                '   <label></label>' +
                '   <span class="info-container">' +
                '     <span class="info"></span>' +
                '     <button type="button" class="btn clear2 pull-right"></button>' +
                '   </span>' +
                '   <input class="filter" type="text">' +
                '   <div class="btn-group buttons">' +
                '     <button type="button" class="btn remove">' +
                '       <i></i>' +
                '     </button>' +
                '     <button type="button" class="btn removeall">' +
                '       <i></i>' +
                '       <i></i>' +
                '     </button>' +
                '   </div>' +
                '   <select multiple="multiple"></select>' +
                ' </div>' +
                '</div>')
                .insertBefore(this.element);

            // Cache the inner elements
            this.elements = {
                originalSelect: this.element,
                box1: $('.box1', this.container),
                box2: $('.box2', this.container),
                filterInput1: $('.box1 .filter', this.container),
                filterInput2: $('.box2 .filter', this.container),
                filterClear1: $('.box1 .clear1', this.container),
                filterClear2: $('.box2 .clear2', this.container),
                label1: $('.box1 > label', this.container),
                label2: $('.box2 > label', this.container),
                info1: $('.box1 .info', this.container),
                info2: $('.box2 .info', this.container),
                select1: $('.box1 select', this.container),
                select2: $('.box2 select', this.container),
                moveButton: $('.box1 .move', this.container),
                removeButton: $('.box2 .remove', this.container),
                moveAllButton: $('.box1 .moveall', this.container),
                removeAllButton: $('.box2 .removeall', this.container),
                form: $($('.box1 .filter', this.container)[0].form)
            };

            // Set select IDs
            this.originalSelectName = this.element.attr('name') || '';
            var select1Id = 'bootstrap-duallistbox-nonselected-list_' + this.originalSelectName,
                select2Id = 'bootstrap-duallistbox-selected-list_' + this.originalSelectName;
            this.elements.select1.attr('id', select1Id);
            this.elements.select2.attr('id', select2Id);
            this.elements.label1.attr('for', select1Id);
            this.elements.label2.attr('for', select2Id);

            // Apply all settings
            this.selectedElements = 0;
            this.elementCount = 0;
            this.setBootstrap2Compatible(this.settings.bootstrap2Compatible);
            this.setFilterTextClear(this.settings.filterTextClear);
            this.setFilterPlaceHolder(this.settings.filterPlaceHolder);
            this.setMoveSelectedLabel(this.settings.moveSelectedLabel);
            this.setMoveAllLabel(this.settings.moveAllLabel);
            this.setRemoveSelectedLabel(this.settings.removeSelectedLabel);
            this.setRemoveAllLabel(this.settings.removeAllLabel);
            this.setMoveOnSelect(this.settings.moveOnSelect);
            this.setPreserveSelectionOnMove(this.settings.preserveSelectionOnMove);
            this.setSelectedListLabel(this.settings.selectedListLabel);
            this.setNonSelectedListLabel(this.settings.nonSelectedListLabel);
            this.setHelperSelectNamePostfix(this.settings.helperSelectNamePostfix);
            this.setSelectOrMinimalHeight(this.settings.selectorMinimalHeight);

            updateSelectionStates(this);

            this.setShowFilterInputs(this.settings.showFilterInputs);
            this.setNonSelectedFilter(this.settings.nonSelectedFilter);
            this.setSelectedFilter(this.settings.selectedFilter);
            this.setInfoText(this.settings.infoText);
            this.setInfoTextFiltered(this.settings.infoTextFiltered);
            this.setInfoTextEmpty(this.settings.infoTextEmpty);
            this.setFilterOnValues(this.settings.filterOnValues);

            // Hide the original select
            this.element.hide();

            bindEvents(this);
            refreshSelects(this);

            return this.element;
        },
        setBootstrap2Compatible: function (value, refresh) {
            this.settings.bootstrap2Compatible = value;
            if (value) {
                this.container.removeClass('row').addClass('row-fluid bs2compatible');
                this.container.find('.box1, .box2').removeClass('col-md-6').addClass('span6');
                this.container.find('.clear1, .clear2').removeClass('btn-default btn-xs').addClass('btn-mini');
                this.container.find('input, select').removeClass('form-control');
                this.container.find('.btn').removeClass('btn-default');
                this.container.find('.moveall > i, .move > i').removeClass('fa fa-arrow-right').addClass('icon-arrow-right');//ACE
                this.container.find('.removeall > i, .remove > i').removeClass('fa fa-arrow-left').addClass('icon-arrow-left');//ACE
            } else {
                this.container.removeClass('row-fluid bs2compatible').addClass('row');
                this.container.find('.box1, .box2').removeClass('span6').addClass('col-md-6');
                this.container.find('.clear1, .clear2').removeClass('btn-mini').addClass('btn-default btn-xs');
                this.container.find('input, select').addClass('form-control');
                this.container.find('.btn').addClass(this.settings.buttonClass)//ACE;//s.addClass('btn-default');
                this.container.find('.moveall > i, .move > i').removeClass('icon-arrow-right').addClass('fa fa-arrow-right');//ACE
                this.container.find('.removeall > i, .remove > i').removeClass('icon-arrow-left').addClass('fa fa-arrow-left');//ACE
            }
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setFilterTextClear: function (value, refresh) {
            this.settings.filterTextClear = value;
            this.elements.filterClear1.html(value);
            this.elements.filterClear2.html(value);
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setFilterPlaceHolder: function (value, refresh) {
            this.settings.filterPlaceHolder = value;
            this.elements.filterInput1.attr('placeholder', value);
            this.elements.filterInput2.attr('placeholder', value);
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setMoveSelectedLabel: function (value, refresh) {
            this.settings.moveSelectedLabel = value;
            this.elements.moveButton.attr('title', value);
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setMoveAllLabel: function (value, refresh) {
            this.settings.moveAllLabel = value;
            this.elements.moveAllButton.attr('title', value);
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setRemoveSelectedLabel: function (value, refresh) {
            this.settings.removeSelectedLabel = value;
            this.elements.removeButton.attr('title', value);
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setRemoveAllLabel: function (value, refresh) {
            this.settings.removeAllLabel = value;
            this.elements.removeAllButton.attr('title', value);
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setMoveOnSelect: function (value, refresh) {
            if (isBuggyAndroid) {
                value = true;
            }
            this.settings.moveOnSelect = value;
            if (this.settings.moveOnSelect) {
                this.container.addClass('moveonselect');
                var self = this;
                this.elements.select1.on('change', function () {
                    move(self);
                });
                this.elements.select2.on('change', function () {
                    remove(self);
                });
            } else {
                this.container.removeClass('moveonselect');
                this.elements.select1.off('change');
                this.elements.select2.off('change');
            }
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setPreserveSelectionOnMove: function (value, refresh) {
            // We are forcing to move on select and disabling preserveSelectionOnMove on Android
            if (isBuggyAndroid) {
                value = false;
            }
            this.settings.preserveSelectionOnMove = value;
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setSelectedListLabel: function (value, refresh) {
            this.settings.selectedListLabel = value;
            if (value) {
                this.elements.label2.show().html(value);
            } else {
                this.elements.label2.hide().html(value);
            }
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setNonSelectedListLabel: function (value, refresh) {
            this.settings.nonSelectedListLabel = value;
            if (value) {
                this.elements.label1.show().html(value);
            } else {
                this.elements.label1.hide().html(value);
            }
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setHelperSelectNamePostfix: function (value, refresh) {
            this.settings.helperSelectNamePostfix = value;
            if (value) {
                this.elements.select1.attr('name', this.originalSelectName + value + '1');
                this.elements.select2.attr('name', this.originalSelectName + value + '2');
            } else {
                this.elements.select1.removeAttr('name');
                this.elements.select2.removeAttr('name');
            }
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setSelectOrMinimalHeight: function (value, refresh) {
            this.settings.selectorMinimalHeight = value;
            var height = this.element.height();
            if (this.element.height() < value) {
                height = value;
            }
            this.elements.select1.height(height);
            this.elements.select2.height(height);
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setShowFilterInputs: function (value, refresh) {
            if (!value) {
                this.setNonSelectedFilter('');
                this.setSelectedFilter('');
                refreshSelects(this);
                this.elements.filterInput1.hide();
                this.elements.filterInput2.hide();
            } else {
                this.elements.filterInput1.show();
                this.elements.filterInput2.show();
            }
            this.settings.showFilterInputs = value;
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setNonSelectedFilter: function (value, refresh) {
            if (this.settings.showFilterInputs) {
                this.settings.nonSelectedFilter = value;
                this.elements.filterInput1.val(value);
                if (refresh) {
                    refreshSelects(this);
                }
                return this.element;
            }
        },
        setSelectedFilter: function (value, refresh) {
            if (this.settings.showFilterInputs) {
                this.settings.selectedFilter = value;
                this.elements.filterInput2.val(value);
                if (refresh) {
                    refreshSelects(this);
                }
                return this.element;
            }
        },
        setInfoText: function (value, refresh) {
            this.settings.infoText = value;
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setInfoTextFiltered: function (value, refresh) {
            this.settings.infoTextFiltered = value;
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setInfoTextEmpty: function (value, refresh) {
            this.settings.infoTextEmpty = value;
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        setFilterOnValues: function (value, refresh) {
            this.settings.filterOnValues = value;
            if (refresh) {
                refreshSelects(this);
            }
            return this.element;
        },
        getContainer: function () {
            return this.container;
        },
        refresh: function (mustClearSelections) {
            updateSelectionStates(this);

            if (!mustClearSelections) {
                saveSelections(this, 1);
                saveSelections(this, 2);
            } else {
                clearSelections(this);
            }

            refreshSelects(this);
        },
        destroy: function () {
            this.container.remove();
            this.element.show();
            $.data(this, 'plugin_' + pluginName, null);
            return this.element;
        }
    };

    // A really lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[pluginName] = function (options) {
        var args = arguments;

        // Is the first parameter an object (options), or was omitted, instantiate a new instance of the plugin.
        if (options === undefined || typeof options === 'object') {
            return this.each(function () {
                // If this is not a select
                if (!$(this).is('select')) {
                    $(this).find('select').each(function (index, item) {
                        // For each nested select, instantiate the Dual List Box
                        $(item).bootstrapDualListbox(options);
                    });
                } else if (!$.data(this, 'plugin_' + pluginName)) {
                    // Only allow the plugin to be instantiated once so we check that the element has no plugin instantiation yet

                    // if it has no instance, create a new one, pass options to our plugin constructor,
                    // and store the plugin instance in the elements jQuery data object.
                    $.data(this, 'plugin_' + pluginName, new BootstrapDualListbox(this, options));
                }
            });
            // If the first parameter is a string and it doesn't start with an underscore or "contains" the `init`-function,
            // treat this as a call to a public method.
        } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {

            // Cache the method call to make it possible to return a value
            var returns;

            this.each(function () {
                var instance = $.data(this, 'plugin_' + pluginName);
                // Tests that there's already a plugin-instance and checks that the requested public method exists
                if (instance instanceof BootstrapDualListbox && typeof instance[options] === 'function') {
                    // Call the method of our plugin instance, and pass it the supplied arguments.
                    returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
                }
            });

            // If the earlier cached method gives a value back return the value,
            // otherwise return this to preserve chainability.
            return returns !== undefined ? returns : this;
        }

    };

})(jQuery, window, document);
